#import "Newsgroup.h"
#import "Article.h"
#import "NNTP.h"
#import "Interval.h"
#import "ColumnMatrix.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#import "GrayCell.h"
#import "descriptors.h"
#import <ctype.h>
#import "response_codes.h"
#import "rfc822realname.h"
#import "KillFile.h"
#import "Alexandra.h"

static int sortType=SORT_BY_NAME;

@implementation Newsgroup

+ setSortType:(int)t
{
	sortType=t;
	
	return self;
}

+ (int)sortType
{
	return sortType;
}

- initTextCell:(const char *)aString
{
   subscribed=FALSE;
   [super initTextCell:aString];
   [self setWrap:NO];
   numUnreadArticles=0;
   articleList=[[MiscSortedList alloc] init];
   [articleList setSortEnabled:FALSE];
   [articleList setSortOrder:Misc_ASCENDING];

   intervalList=[[List alloc] init];
   delayed=TRUE;
   min=0;
   max=-1;
	first_unread=0;
	
   return self;
}

- updateArticles
{
   int i,j=[articleList count];
	for(i=0;i<j;i++)
		[[articleList objectAt:i] composeTitle];
		
	return self;
}

- free
{
   if(articleList!=nil){
      [articleList makeObjectsPerform:@selector(free)];
      [articleList free];
   }
   if(intervalList!=nil){
      [intervalList makeObjectsPerform:@selector(free)];
      [intervalList free];
   }
   [super free];
   return self;
}

- setSubscribed
{
   subscribed=TRUE;
   return self;
}

- setUnsubscribed
{
   subscribed=FALSE;
   return self;
}

- (BOOL)isSubscribed
{
   return subscribed;
}

- setNumUnreadArticles:(int)num
{
   numUnreadArticles=(unsigned)num;
   return self;
}

- incNumberUnreadArticles:(int)delta
{
   numUnreadArticles+=delta;
   return self;
}

- approxNumUnread
{
   long i;

   numUnreadArticles=0;
   if((min==0)&&(max==0))
      return self;
   for(i=min;i<=max;i++)
      if([self inReadList:i]==FALSE)
         numUnreadArticles++;
   return self;
}

- (long)numberUnreadArticles
{
   return numUnreadArticles;
}

- (int)markAllReadUntil:lastArticle
{
   int i,j,ii=0;
   Article *anArticle;

   if(delayed==TRUE){
      if(intervalList!=nil){
         [intervalList makeObjectsPerform:@selector(free)];
         [intervalList empty];
      }
      else
         intervalList=[[List alloc] init];
      if(min<=max)
         [intervalList addObject:[[Interval alloc] initWithLower:min upper:max]];
      [self setNumUnreadArticles:0];
   }
   else{
      for(i=0,j=[articleList count];i<j;i++){
         anArticle=[articleList objectAt:i];
         if([anArticle isRead]==FALSE){
            ii++;
            [anArticle setRead];
         }
         if(anArticle==lastArticle)
            break;
      }
      if(ii>0)
         [self incNumberUnreadArticles:(-1)*ii];
   }

   return ii;
}

- setTextAttributes:textObj
{
   [super setTextAttributes:textObj];
   if (subscribed==FALSE)
      [textObj setTextGray:NX_DKGRAY];
   else
      [textObj setTextGray:NX_BLACK];      
   return textObj;
}

- drawInside:(const NXRect *)cellFrame inView:controlView
{
   NXRect numrect;
   static id sharedTextCell = nil;
   char numstr[40];
    
    //erase cell
    PSsetgray((cFlags1.state || cFlags1.highlighted) ? NX_WHITE : NX_LTGRAY);
    NXRectFill(cellFrame);
 
    //make cell
    if (!sharedTextCell) {
        sharedTextCell = [[GrayCell alloc] init];
        [sharedTextCell setWrap:NO];
    }
   
   // draw text
   if(subscribed==TRUE)
      [sharedTextCell setDrawGray:FALSE];
   else
      [sharedTextCell setDrawGray:TRUE];
   [sharedTextCell setFont:[self font]];
   [sharedTextCell setStringValueNoCopy:[self stringValue]];
   [sharedTextCell drawInside:cellFrame inView:controlView];
   
   // draw number unread articles to the right
   if(numUnreadArticles!=0){
      sprintf(numstr,"%d",numUnreadArticles);
      [sharedTextCell setStringValue:numstr];
      NX_WIDTH(&numrect)=[[sharedTextCell font] getWidthOf:numstr]+4.0;
      NX_HEIGHT(&numrect)=NX_HEIGHT(cellFrame);
      NX_X(&numrect)=NX_X(cellFrame)+NX_WIDTH(cellFrame)- NX_WIDTH(&numrect);
      NX_Y(&numrect)=NX_Y(cellFrame);
      PSsetgray((cFlags1.state || cFlags1.highlighted) ? NX_WHITE : NX_LTGRAY);
      NXRectFill(&numrect);
      [sharedTextCell drawInside:&numrect inView:controlView];
   }

   PSsetgray(NX_DKGRAY);
   if (cFlags1.state || cFlags1.highlighted){
      NXRect rectArray[2];

      NXSetRect(&(rectArray[0]),NX_X(cellFrame),NX_Y(cellFrame), NX_WIDTH(cellFrame),1);
      NXSetRect(&(rectArray[1]),NX_X(cellFrame),NX_MAXY(cellFrame)-1,
NX_WIDTH(cellFrame), 1.0);
      NXRectFillList(rectArray, 2);
   }

   return self;
}

- highlight:(const NXRect *)cellFrame inView:controlView lit:(BOOL)flag
{
    if(cFlags1.highlighted != flag){
       cFlags1.highlighted = flag;
       [self drawInside:cellFrame inView:controlView];
    }
    return self;
}

- setReadList:(char *)readlist
{
   int x,y,i;
   long lower,upper;
   
   if(intervalList==nil)
      intervalList=[[List alloc] init];
   y=strlen(readlist);
   if((y>0)&&(readlist[y-1]=='\n')){
      readlist[y-1]='\0';
      y--;
   }
   i=0;
   while(i<y){
      x=i;
      while((i<y)&&(isdigit(readlist[i])!=0))
         i++;
      if(isdigit(readlist[x])!=0)
         lower=atol(readlist+x);
      else
         lower=0;
      if((i==y)||(readlist[i]==',')){
         upper=lower;
         i++;
      }
      else if(readlist[i]=='-'){
         x=++i;
         while((i<y)&&(isdigit(readlist[i])!=0))
            i++;
         if(isdigit(readlist[x])!=0)
            upper=atol(readlist+x);
         else
            upper=0;
         if(readlist[i]==',')
            i++;
      }
      else{
         i++;
         continue;
      }
      if((upper!=0) && (lower!=0))
         [intervalList addObject:[[Interval alloc] initWithLower:lower upper:upper]];
   }
	
	if([intervalList count]>0 && [NXApp defaultBoolValue:DEFAULT_PRIVATE_EXPIRE])
		first_unread=[[intervalList objectAt:0] upperBound]+1;
	
   return self;
}

- (BOOL)inReadList:(long)number
{
   int i,ii;
   
   ii=[intervalList count];
   for(i=0;i<ii;i++)
      if([[intervalList objectAt:i] isIn:number]==TRUE)
         return TRUE;
   return FALSE;
}

- dumpReadList:(NXStream *)aStream
{

   long smallest,largest;
   BOOL run_started;
   long start,stop;
   long i,j;
   char *bucket;
   long bsize;
   long c=0;

   //ever scanned this newsgroup?
   if(delayed==TRUE){
      j=[intervalList count];
      for(i=0;i<j;i++){
         [[intervalList objectAt:i] dumpAsAsciiIn:aStream];
         if(i<j-1)
            NXWrite(aStream,",",1);
      }
      return self;
   }

   // no articles in this group?
   j=[articleList count];
   if(j==0){
      if(min<max)
         NXPrintf(aStream,"1-%d",max);
      return self;
   }

   //let's start -- first get smallest and largest article number
   smallest=[[articleList objectAt:0] number];
   largest=smallest;
   for(i=0;i<j;i++){
      long num=[[articleList objectAt:i] number];
      if(num<smallest) smallest=num;
      if(num>largest) largest=num;
   }

   //malloc bucket and fill it
   bsize=largest-smallest+2;
   bucket=(char *)calloc(bsize,sizeof(char));
   
   for(i=0;i<j;i++){
      id aCell=[articleList objectAt:i];
      if([aCell isRead]==FALSE)
         bucket[[aCell number]-smallest]=(char)1;
   }
   bucket[bsize-1]=(char)1; //dummy!!!

   //init
   run_started=FALSE;
   start=1;
   stop=0;
   if(smallest!=1){
      run_started=TRUE;
      stop=smallest-1;
   }
   
   for(i=0;i<bsize;i++){
      if(run_started==TRUE){
         if(bucket[i]==(char)1){
            if(start<=stop)
               if(c>0)
                  NXPrintf(aStream,",");
               else c++;
            if(start<stop)
               NXPrintf(aStream,"%d-%d",start,stop);
            else
               if(start==stop)
                  NXPrintf(aStream,"%d",start);
            run_started=FALSE;
         }
         else // bucket==0
            stop++;
      }
      else
         if(bucket[i]==(char)0){
            start=i+smallest;
            stop=start;
            run_started=TRUE;
         }
   }

   free(bucket);

   return self;
}

- scanArticles:(NNTP *)nntpServer
{
   return [self scanArticles:nntpServer visibleIn:nil];
}

- scanArticles:(NNTP *)nntpServer visibleIn:articleView
{
   long oldMin,oldMax;
   long from,to,expired_min=0;
   Article *anArticle;
   int i,j,ii;
   Storage *array;
   subjectDesc *subDesc;
   headerDesc *h;
   char buf[512];

	if([NXApp defaultBoolValue:DEFAULT_PRIVATE_EXPIRE]){
		float a=(float)[NXApp defaultIntValue:DEFAULT_EXPIRE_RELATIV_OFFSET]/100;
		int b=[NXApp defaultIntValue:DEFAULT_EXPIRE_ABSOLUTE_OFFSET];
		long y1,y2,y1cnt=first_unread-min;
		y1= (y1cnt < 1)? min : (long)(first_unread - a*y1cnt);
		y1cnt=first_unread-b;
		y2= y1cnt<0 ? min : y1cnt;
		expired_min=MAX(y1,y2);
	}
	
   oldMin=-1; oldMax=-1;
   from=-1; to=-1;
   j=[articleList count];
   if((delayed==TRUE)||(j==0)){
		if(expired_min>0)
			from=expired_min;
		else
      	from=min;
      to=max;
   }
   else{
      oldMin=[[articleList objectAt:0] number];
      oldMax=oldMin;
      // find min + max
      for(i=0;i<j;i++){
         long num=[[articleList objectAt:i] number];
         if(num>oldMax) oldMax=num;
         if(num<oldMin) oldMin=num;
      }

      // see if there's something to do
      if(max>oldMax){
         from=oldMax+1;
         to=max;
      }
   }

	

   // get new articles?
   if(from!=-1){
      array=[[Storage alloc] initCount:0 elementSize:sizeof(subjectDesc) description:"{l*}"];
		
      [nntpServer fetchSubjectHeaders:array from:from to:to];

      j=[array count];
      for(i=0;i<j;i++){
         subDesc=(subjectDesc *)[array elementAt:(unsigned int)i];

			//Assert a Subject header
			if(!subDesc->fieldBody[SUBJECT])
				subDesc->fieldBody[SUBJECT]=NXCopyStringBuffer("(none)");
			else if(!(*subDesc->fieldBody[SUBJECT])){
				free(subDesc->fieldBody[SUBJECT]);
				subDesc->fieldBody[SUBJECT]=NXCopyStringBuffer("(none)");
			}

         anArticle=[[Article alloc] initWithNumber:subDesc->number];
         h=[anArticle header];
         for(ii=0;ii<XOVER_COUNT;ii++)
            h->fieldBody[ii]=subDesc->fieldBody[ii];
			free(subDesc->fieldBody);
         [anArticle setSize:subDesc->artsize];
         [anArticle setLines:subDesc->lines];
         [anArticle composeTitle];
			
         if([self inReadList:subDesc->number]==TRUE)
            [[anArticle setRead] unsetTag];
         else
            [[anArticle setUnread] setTag];
         [articleList addObject:anArticle];
      } 
      [array free];
   }
   
   // see if there are any expired articles
   if((delayed==FALSE) && (oldMin!=-1) && (min>oldMin))
      for(i=[articleList count]-1;i>=0;i--)
         if([[articleList objectAt:i] number]<min){
            id bogusCell=[articleList objectAt:i];
            if(articleView!=nil)
               [articleView removeInvalidCell:bogusCell andUpdate:FALSE];
            else
               [[articleList removeObjectAt:i] free];
         }
   //get sort order
   sprintf(buf,"SortType %s",[nntpServer serverName]);
   [self resortIfNeeded:[NXApp defaultIntValue:buf]];
   
   //filter articles
   [[nntpServer killFile] filterArticles:self andReloadMatrix:FALSE];

   // calc number unread articles
   numUnreadArticles=0;
   j=[articleList count];
   for(i=0;i<j;i++)
      if([[articleList objectAt:i] isRead]==FALSE)
         numUnreadArticles++;

   delayed=FALSE;
   return self;
}

- (BOOL)isDelayed
{
   return delayed;
}

- (List *)articleList
{
   return articleList;
}
- unsetTag
{
   myTag=0;
   return self;
}

- setTag
{
   myTag=1;
   return self;
}

- (BOOL)isTaged
{
   return(myTag==1);
}

- setMin:(long)newMin
{
	min=newMin;

   return self;
}

- setMax:(long)newMax;
{
   max=newMax;
   return self;
}

- (long)minNumber
{
   return min;
}

- (long)maxNumber
{
   return max;
}

- (BOOL)bogus
{
   return isBogus;
}

- setBogus:(BOOL)b
{
  isBogus=b;
  return self;
}

- setPostable:(char)p
{
   postable=p;
   return self;
}

- (char)postable
{
   return postable;
}

- (BOOL)resortIfNeeded:(int)val
{
   if(![articleList sorted]||(val!=currentSortType)){
		currentSortType=val;
		[Article setSortType:val];
		[articleList unsort];
      [articleList sort];
      return TRUE;
   }
   return FALSE;
} 

//MiscCompare protocol implementation

- (int)compare:anObject
{
	BOOL t;
	int s;
	
   if(anObject==nil) return 0;
	if(sortType==SORT_BY_NAME)
   	return(strcmp([self stringValue],[anObject stringValue]));
	
	t=[anObject isTaged];
	if(t!=[self isTaged])
		return (t? 1 : -1);
	
	if(t){
		s=[anObject state];
		if(s!=[self state])
			return (s==1? 1 : -1);
	}
	
	t=[anObject isSubscribed];
	if(t!=[self isSubscribed])
		return(t? 1 : -1);
	
	t=[anObject isDelayed];	
	if(t!=[self isDelayed])
		return(t? 1 : -1);
		
	return 0;
}	



@end
